/***********************************************************************************/
// Copyright (C) 2021 Meng Naihong.
//
// Licensed under the MIT License (the "License"); you may not use this file except
// in compliance with the License. You may obtain a copy of the License at
//
// http://opensource.org/licenses/MIT
// Contact mengnaihong@foxmail.com for support.
/***********************************************************************************/
#pragma once
#include <stdio.h>
#include <memory>
#include <string>
#include <vector>
#include <functional>
#include "rapidjson/rapidjson.h"
#include "rapidjson/document.h"
#include "rapidjson/writer.h"
#include "rapidjson/StringBuffer.h"

namespace cutar
{
	class PropertyGroup;

	class PropertyBase
	{
	public:
		PropertyBase(std::string name) :m_name(name) {}
		virtual ~PropertyBase() { /*printf("~PropertyBase()!\n");*/ }

		std::string GetName() { return m_name; }

		virtual std::string Serialize() = 0;

		virtual bool DeSerialize(std::string& data) = 0;

		static std::string GetSerializeStringName(std::string& data) 
		{
			rapidjson::Document doc;
			doc.Parse(data.c_str());
			if (!doc.HasParseError() && doc.MemberCount() == 1)
				return doc.MemberBegin()->name.GetString();
			return "";
		}

	protected:
		virtual void WriteValue(rapidjson::Writer<rapidjson::StringBuffer>& writer) {}

	protected:
		std::string m_name;
		friend PropertyGroup;
	};

	template<typename T>
	class PropertyObject : public PropertyBase
	{
	public:
		PropertyObject(std::string name, T defaultVal) :m_data(defaultVal), PropertyBase(name) {}

		PropertyObject(std::string name, T defaultVal, std::vector<PropertyBase*>& list)
			:m_data(defaultVal), PropertyBase(name)
		{
			list.push_back(this);
		}

		void Set(T value, bool bUserSet = true) { m_data = value; }

		T Get() { return m_data; }

		std::string Serialize() override
		{
			rapidjson::StringBuffer strBuffer;
			rapidjson::Writer<rapidjson::StringBuffer> writer(strBuffer);
			writer.StartObject();
			writer.Key(m_name.c_str());

			SerializeValue<T>(writer);

			writer.EndObject();
			return strBuffer.GetString();
		}

		bool DeSerialize(std::string& data) override 
		{
			rapidjson::Document doc;
			doc.Parse(data.c_str());
			if (!doc.HasParseError())
				if (doc.HasMember(m_name.c_str()))
					return DeSerializeValue<T>(doc[m_name.c_str()]);
			return false;
		}

		static std::string CreateJson(std::string name, T val) 
		{
			PropertyObject<T> obj(name, val);
			return obj.Serialize();
		}

		static PropertyObject<T> CreateObject(std::string& data) 
		{
			PropertyObject<T> obj;
			obj.m_name = GetSerializeStringName(data);
			obj.DeSerialize(data);
			return obj;
		}

	protected:
		void WriteValue(rapidjson::Writer< rapidjson::StringBuffer>& writer) override
		{
			SerializeValue<T>(writer);
		}

		template<typename T>
		void SerializeValue(rapidjson::Writer<rapidjson::StringBuffer>& writer);

		template<>
		void SerializeValue<bool>(rapidjson::Writer<rapidjson::StringBuffer>& writer) { writer.Bool(m_data); }

		template<>
		void SerializeValue<int>(rapidjson::Writer<rapidjson::StringBuffer>& writer) { writer.Int(m_data); }

		template<>
		void SerializeValue<long>(rapidjson::Writer<rapidjson::StringBuffer>& writer) { writer.Int64(m_data); }

		template<>
		void SerializeValue<float>(rapidjson::Writer<rapidjson::StringBuffer>& writer) { writer.Double(m_data); }

		template<>
		void SerializeValue<double>(rapidjson::Writer<rapidjson::StringBuffer>& writer) { writer.Double(m_data); }

		template<>
		void SerializeValue<std::string>(rapidjson::Writer<rapidjson::StringBuffer>& writer) { writer.String(m_data.c_str()); }

		template<typename T>
		bool DeSerializeValue(rapidjson::Value& val);

		template<>
		bool DeSerializeValue<bool>(rapidjson::Value& val) { if (val.IsBool()) Set(val.GetBool(), false); return val.IsBool(); }

		template<>
		bool DeSerializeValue<int>(rapidjson::Value& val) { if (val.IsInt()) Set(val.GetInt(), false); return val.IsInt(); }

		template<>
		bool DeSerializeValue<long>(rapidjson::Value& val) { if (val.IsInt64()) Set(val.GetInt64(), false); return val.IsInt64(); }

		template<>
		bool DeSerializeValue<float>(rapidjson::Value& val) { if (val.IsFloat()) Set(val.GetFloat(), false); return val.IsFloat(); }

		template<>
		bool DeSerializeValue<double>(rapidjson::Value& val) { if (val.IsDouble()) Set(val.GetDouble(), false); return val.IsDouble(); }

		template<>
		bool DeSerializeValue<std::string>(rapidjson::Value& val) { if (val.IsString()) Set(val.GetString(), false); return val.IsString(); }

	private:
		PropertyObject() :PropertyBase("") {}

	private:
		T m_data;
	};

	template<typename T>
	class PropertyArray : public PropertyBase
	{
	public:
		PropertyArray(std::string name) :PropertyBase(name) {}
		PropertyArray(std::string name, std::vector<PropertyBase*>& objs)
			:PropertyBase(name)
		{
			objs.push_back(this);
		}

		void Add(T& val) { m_list.push_back(val); }

		void Clear() { m_list.clear(); }

		std::vector<T> GetArray() { return m_list; }

		static PropertyArray<T> CreateArray(std::string& data) 
		{
			std::string sName = PropertyBase::GetSerializeStringName(data);
			PropertyArray<T> objs(sName);
			objs.DeSerialize(data);
			return objs;
		}

		virtual std::string Serialize() override
		{
			if (m_list.size() > 0)
			{
				rapidjson::StringBuffer strBuffer;
				rapidjson::Writer<rapidjson::StringBuffer> writer(strBuffer);
				writer.StartObject();
				writer.Key(m_name.c_str());
				writer.StartArray();

				SerializeValue<T>(writer);

				writer.EndArray();
				writer.EndObject();
				return strBuffer.GetString();
			}
			return "";
		}

		virtual bool DeSerialize(std::string& data) override
		{
			rapidjson::Document doc;
			doc.Parse(data.c_str());
			const char* name = m_name.c_str();
			if (!doc.HasParseError())
				if (doc.HasMember(name) && doc[name].IsArray())
				{
					m_list.clear();
					rapidjson::Value& arrays = doc[name];

					bool bRet = true;
					for (rapidjson::Value& obj : arrays.GetArray()) {
						if (!DeSerializeValue<T>(obj))
							bRet = false;
					}
					return bRet;
				}
			return false;
		}

	protected:
		virtual void WriteValue(rapidjson::Writer< rapidjson::StringBuffer>& writer) override
		{
			writer.StartArray();
			SerializeValue<T>(writer);
			writer.EndArray();
		}

		template<typename T>
		void SerializeValue(rapidjson::Writer<rapidjson::StringBuffer>& writer);

		template<>
		void SerializeValue<bool>(rapidjson::Writer<rapidjson::StringBuffer>& writer) { for (auto member : m_list) writer.Bool(member); }

		template<>
		void SerializeValue<int>(rapidjson::Writer<rapidjson::StringBuffer>& writer) { for (auto member : m_list) writer.Int(member); }

		template<>
		void SerializeValue<long>(rapidjson::Writer<rapidjson::StringBuffer>& writer) { for (auto member : m_list) writer.Int(member); }

		template<>
		void SerializeValue<float>(rapidjson::Writer<rapidjson::StringBuffer>& writer) { for (auto member : m_list) writer.Double(member); }

		template<>
		void SerializeValue<double>(rapidjson::Writer<rapidjson::StringBuffer>& writer) { for (auto member : m_list) writer.Double(member); }

		template<>
		void SerializeValue<std::string>(rapidjson::Writer<rapidjson::StringBuffer>& writer) { for (auto member : m_list) writer.String(member.c_str()); }

		template<typename T>
		bool DeSerializeValue(rapidjson::Value& val);

		template<>
		bool DeSerializeValue<bool>(rapidjson::Value& val) { if (val.IsBool()) m_list.push_back(val.GetBool()); return val.IsBool(); }

		template<>
		bool DeSerializeValue<int>(rapidjson::Value& val) { if (val.IsInt()) m_list.push_back(val.GetInt()); return val.IsInt(); }

		template<>
		bool DeSerializeValue<long>(rapidjson::Value& val) { if (val.IsInt64()) m_list.push_back(val.GetInt64()); return val.IsInt64(); }

		template<>
		bool DeSerializeValue<float>(rapidjson::Value& val) { if (val.IsFloat()) m_list.push_back(val.GetFloat()); return val.IsFloat(); }

		template<>
		bool DeSerializeValue<double>(rapidjson::Value& val) { if (val.IsDouble()) m_list.push_back(val.GetDouble()); return val.IsDouble(); }

		template<>
		bool DeSerializeValue<std::string>(rapidjson::Value& val) { if (val.IsString()) m_list.push_back(val.GetString()); return val.IsString(); }

	private:
		std::vector<T> m_list;
	};
	
	class PropertyGroup
	{
	public:
		PropertyGroup() {}
		virtual ~PropertyGroup()
		{
			//for (PropertyBase* obj : m_objs) { delete obj; }
			m_objs.clear();
		}
		virtual std::string GetGroupName() { return ""; }

		std::string ToSerializeString(bool bWithGroupName = true)
		{
			std::string data;
			std::string tail;
			std::string sNameSpace = GetGroupName();
			if (sNameSpace.empty() == false && bWithGroupName)
			{
				data.append("{\"");
				data.append(sNameSpace);
				data.append("\":");
				tail = "}";
			}
			data.append("{");
			bool bHasValidItem = false;
			for (auto obj : m_objs) {
				std::string sJson = obj->Serialize();

				//HasValidItem
				if (sJson.size() > 2)
				{
					bHasValidItem = true;
					data.append(sJson.substr(1, sJson.size() - 2));
					data.append(",");
				}
			}
			if (m_objs.size() > 0 && bHasValidItem)
				data.pop_back();

			data.append("}");
			data.append(tail);
			return data;
		}

		bool FromSerializeString(std::string& json, bool bWithGroupName = true)
		{
			std::string data = json;
			rapidjson::Document doc;
			doc.Parse(json.c_str());

			bool bRet = true;
			if (doc.HasParseError())
			{
				printf("PropertyGroup::ParseError Code=%d, ErrorOffset=%d\n", doc.GetParseError(), doc.GetErrorOffset());
				return false;
			}

			std::string sGroupName = GetGroupName();
			if (sGroupName.empty() == false && bWithGroupName)
			{
				if (doc.HasMember(sGroupName.c_str()) && doc[sGroupName.c_str()].IsObject())
				{
					rapidjson::StringBuffer buffer;
					rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
					rapidjson::Value& objs = doc[sGroupName.c_str()];
					objs.Accept(writer);
					data = buffer.GetString();
				}
			}

			for (auto obj : m_objs)
			{
				if (!obj->DeSerialize(data))
				{
					bRet = false;
					printf("PropertyGroup::FromSerializeString() %s DeSerialize failed!", obj->GetName().c_str());
				}
			}

			return bRet;
		}

		void WriteValue(rapidjson::Writer<rapidjson::StringBuffer>& writer)
		{
			writer.StartObject();
			for (auto obj : m_objs) {
				writer.Key(obj->GetName().c_str());
				obj->WriteValue(writer);
			}
			writer.EndObject();
		}

	protected:
		std::vector<PropertyBase*> m_objs;
	};
	using PropertyGroupShardPtr = std::shared_ptr<PropertyGroup>;

	class PropertyGroupArray: public PropertyBase
	{
	public:
		PropertyGroupArray(std::string name) : PropertyBase(name) {}
		

		void Add(PropertyGroup& val) { PropertyGroupShardPtr obj(new PropertyGroup(val)); m_list.push_back(obj); }

		void Clear() { m_list.clear(); }

		template<typename T>
		std::vector<std::shared_ptr<T>> GetArray()
		{
			std::vector<std::shared_ptr<T>> list;
			for(auto obj : m_list)
			{
				std::shared_ptr<T> realSp =  std::dynamic_pointer_cast<T>(obj);
				list.push_back(realSp);
			}
			return list; }

		std::string ToSerializeString()
		{
			return Serialize();
		}

		template<typename T>
		bool FromSerializeString(std::string& json, bool bWithGroupName = true)
		{
			rapidjson::Document doc;
			doc.Parse(json.c_str());
			const char* name = m_name.c_str();
			if (doc.HasParseError())
				return false;
			else if (bWithGroupName && !doc.HasMember(name))
					return false;

			if (doc[name].IsArray())
			{
				m_list.clear();
				rapidjson::Value& arrays = doc[name];

				bool bRet = true;
				for (rapidjson::Value& obj : arrays.GetArray()) {
					if (!DeSerializeValue<T>(obj))
						bRet = false;
				}
				return bRet;
			}
			return false;
		}

		template<typename T>
		bool DeSerializeValue(rapidjson::Value& val)
		{
			if (val.IsObject())
			{
				rapidjson::StringBuffer buffer;
				rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
				val.Accept(writer);
				std::string data = buffer.GetString();
				T obj;
				PropertyGroupShardPtr sp(new T(obj)); 
				if (sp->FromSerializeString(data, false))
				{
					m_list.push_back(sp);
					return true;
				}
			}
			return false;
		}

	protected:
		virtual std::string Serialize() override 
		{
			if (m_list.size() > 0)
			{
				rapidjson::StringBuffer strBuffer;
				rapidjson::Writer<rapidjson::StringBuffer> writer(strBuffer);
				writer.StartObject();
				writer.Key(m_name.c_str());
				writer.StartArray();

				///SerializeValue<T>(writer);
				for (auto member : m_list)
					member->WriteValue(writer);

				writer.EndArray();
				writer.EndObject();
				return strBuffer.GetString();
			}
			return "";
		}

		virtual bool DeSerialize(std::string& data) override
		{
			return false;
		}

	private:
		std::vector<PropertyGroupShardPtr> m_list;
	};
}


#define GroupName(groupName) \
	public: \
	virtual std::string GetGroupName() { return groupName; } \
	static std::string GroupName(){return groupName;} 

#define Property(name, type, defaultVal) \
	private: \
		std::shared_ptr<PropertyObject<type>> m_##name = std::shared_ptr<PropertyObject<type>>(new PropertyObject<type>(#name, defaultVal, m_objs)); \
	public: \
		type Get##name(){ return m_##name->Get();} \
		void Set##name(type value){m_##name->Set(value);}

#define PropertyArray(name, type) \
	private: \
		std::shared_ptr<PropertyArray<type>> m_array##name = std::shared_ptr<PropertyArray<type>>(new PropertyArray<type>(#name, m_objs)); \
	public: \
		void Add##name(type val){m_array##name->Add(val);} \
		std::vector<type> Get##name(){return m_array##name->GetArray();}
